/*
# pymoz, Python extension for the Mozilla Toolkit
# Copyright (C) 2012 Malek Hadj-Ali
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include "pyRuntime.h"
#include "pyException.h"
#include "pyWrappers.h"

#include "nsIChromeRegistry.h"
#include "nsIObserverService.h"
#include "nsIConsoleService.h"
#include "xpcIJSModuleLoader.h"

#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"


/*******************************************************************************
 utils
*******************************************************************************/

static PLDHashOperator
ModuleCache_Clear(const nsACString& key, pyModule *module, void *closure)
{
    module->Clear();
    return PL_DHASH_NEXT;
}


static PLDHashOperator
PyObjectCache_Clear(JSObject *jsobj, PyObject *pyobj, void *closure)
{
    Py_DECREF(pyobj);
    return PL_DHASH_NEXT;
}


static void
PyDict_CleanupJSObject(PyObject *dict)
{
    PyObject *key, *value;
    Py_ssize_t pos = 0;
    while (PyDict_Next(dict, &pos, &key, &value)) {
        if (PyJSObject_Check(value) && PyDict_SetItem(dict, key, Py_None)) {
            PyErr_Print();
        }
    }
}


static void
JSGlobal_CleanupPyObject(JSContext *acx)
{
    JSAutoRequest ar(acx);
    JSObject *aglob = JS_GetGlobalObject(acx);
    if (aglob) {
        JS::AutoIdArray aida(acx, JS_Enumerate(acx, aglob));
        if (!aida) {
            return;
        }
        size_t alength = aida.length();
        for (size_t i = 0; i < alength; ++i) {
            jsid aid = aida[i];
            jsval aval;
            if (JS_GetPropertyById(acx, aglob, aid, &aval) &&
                !JSVAL_IS_PRIMITIVE(aval) &&
                JSPyObject_Check(JSVAL_TO_OBJECT(aval))) {
                JS_DeletePropertyById(acx, aglob, aid);
            }
        }
    }
}


static int
PymozGCRoot_Clear(void *rp, JSGCRootType type, const char *name, void *data)
{
    int result = JS_MAP_GCROOT_NEXT;

    if (!strcmp(name, "pymoz")) {
        result |= JS_MAP_GCROOT_REMOVE;
    }
    return result;
}


/*******************************************************************************
* pyRuntime
*******************************************************************************/

NS_IMPL_ISUPPORTS3(pyRuntime, pyIRuntime, nsIObserver, mozilla::ModuleLoader)


pyRuntime *pyRuntime::sPyRuntime = NULL;


/* interface pyIRuntime ----------------------------------------------------- */

NS_IMETHODIMP
pyRuntime::GetBuiltins(JSContext *cx, JS::Value *aBuiltins)
{
    JSAutoContextPusher acp(cx);
    mResult = NS_ERROR_FAILURE;

    PyObject *builtins = PyImport_ImportModule("builtins");
    if (builtins) {
        *aBuiltins = PyObject_AsJSVal(cx, builtins);
        Py_DECREF(builtins);
        if (!JSVAL_IS_VOID(*aBuiltins)) {
           mResult = NS_OK;
        }
    }

    if (NS_FAILED(mResult)) {
        ReportError();
    }
    return mResult;
}


NS_IMETHODIMP
pyRuntime::Run(const nsACString& aSpec, const char *aOriginCharset,
               nsIURI *aBaseURI, JSContext *cx)
{
    JSAutoContextPusher acp(cx);
    mResult = NS_ERROR_FAILURE;
    nsCString aPath;

    nsCOMPtr<nsIFile> aFile = GetFile(aSpec, aOriginCharset, aBaseURI, aPath);
    if (aFile) {
        const char *path = aPath.get();
        FILE *fp;
        if (NS_FAILED(aFile->OpenANSIFileDesc("r", &fp))) {
            PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
        }
        else {
            mResult = NS_OK;
            if (!PyRun_SimpleFile(fp, path) && !UpdateJSGlobal()) {
                mResult = NS_ERROR_FAILURE;
            }
            fclose(fp);
        }
    }

    if (NS_FAILED(mResult)) {
        ReportError();
    }
    return mResult;
}


NS_IMETHODIMP
pyRuntime::ImportModule(const nsACString& aName, JSContext *cx,
                        JS::Value *_retval)
{
    JSAutoContextPusher acp(cx);
    mResult = NS_ERROR_FAILURE;

    char *name = ToNewCString(aName);
    if (name) {
        PyObject *module = PyImport_ImportModule(name);
        if (module) {
            *_retval = PyObject_AsJSVal(cx, module);
            Py_DECREF(module);
            if (!JSVAL_IS_VOID(*_retval)) {
               mResult = NS_OK;
            }
        }
        NS_Free(name);
    }

    if (NS_FAILED(mResult)) {
        ReportError();
    }
    return mResult;
}


NS_IMETHODIMP
pyRuntime::Compare(const JS::Value& obj1, const JS::Value& obj2, int32_t op,
                   JSContext *cx, JS::Value *_retval)
{
    JSAutoContextPusher acp(cx);
    mResult = NS_ERROR_FAILURE;

    PyObject *pyobj1 = PyObject_FromJSVal(cx, obj1);
    PyObject *pyobj2 = PyObject_FromJSVal(cx, obj2);
    if (pyobj1 && pyobj2) {
        PyObject *pyresult = PyObject_RichCompare(pyobj1, pyobj2, op);
        if (pyresult) {
            *_retval = PyObject_AsJSVal(cx, pyresult);
            Py_DECREF(pyresult);
            if (!JSVAL_IS_VOID(*_retval)) {
               mResult = NS_OK;
            }
        }
    }
    Py_XDECREF(pyobj2);
    Py_XDECREF(pyobj1);

    if (NS_FAILED(mResult)) {
        ReportError();
    }
    return mResult;
}


/* interface nsIObserver ---------------------------------------------------- */

NS_IMETHODIMP
pyRuntime::Observe(nsISupports *aSubject, const char *aTopic,
                   const PRUnichar *aData)
{
    if (!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) {
        nsCOMPtr<nsIObserverService> obsSvc =
            do_GetService("@mozilla.org/observer-service;1");
        if (obsSvc) {
            obsSvc->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
        }
        Clear();
    }

    return NS_OK;
}


/* interface mozilla::ModuleLoader ------------------------------------------ */

const mozilla::Module *
pyRuntime::LoadModule(mozilla::FileLocation& aFileLocation)
{
    nsCString aPath;
    pyModule *aModule = NULL;

    if (aFileLocation.IsZip()) {
        PyErr_SetString(mError, "Cannot load module from zip archive");
        goto finish;
    }

    if (NS_FAILED(aFileLocation.mBaseFile->GetNativePath(aPath))) {
        PyErr_SetString(mXPCOMError, "Failed to get native path");
        goto finish;
    }

    if (!mModules.Get(aPath, &aModule)) {
        aModule = new pyModule();
        if (!aModule) {
            PyErr_NoMemory();
            goto finish;
        }
        if (!aModule->Init(aFileLocation.mBaseFile, aPath.get())) {
            delete aModule;
            aModule = NULL;
            goto finish;
        }
        mModules.Put(aPath, aModule);
    }

finish:
    mResult = aModule ? NS_OK : NS_ERROR_FAILURE;

    if (NS_FAILED(mResult)) {
        ReportError();
    }

    return aModule;
}


/* pyRuntime - Initialization/Finalization ---------------------------------- */

pyRuntime::pyRuntime()
    : mError(NULL),
      mJSError(NULL),
      mXPCOMError(NULL),
      mJSWarning(NULL),
      mJSPyTypeProto(NULL),
      mXPCOMUtils(NULL),
      mComponents(NULL),
      mResult(NS_ERROR_FAILURE),
      mJSRuntime(NULL)
{
}


pyRuntime::~pyRuntime()
{
    Clear();
}


pyRuntime *
pyRuntime::GetService()
{
    if (!sPyRuntime) {
        sPyRuntime = new pyRuntime();
        if (sPyRuntime && !sPyRuntime->Setup()) {
            delete sPyRuntime;
            sPyRuntime = NULL;
        }
    }

    NS_IF_ADDREF(sPyRuntime);
    return sPyRuntime;
}


bool
pyRuntime::PreSetup()
{
    nsresult rv = NS_ERROR_FAILURE;
    _Py_IDENTIFIER(excepthook);

    // XPConnect
    mXPConnect = do_GetService(nsIXPConnect::GetCID(), &rv);
    NS_ENSURE_SUCCESS(rv, false);
    rv = mXPConnect->SetReportAllJSExceptions(true);
    NS_ENSURE_SUCCESS(rv, false);

    // ExceptionManager
    mExceptionService = do_GetService(NS_EXCEPTIONSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, false);
    rv = mExceptionService->GetCurrentExceptionManager(getter_AddRefs(mExceptionManager));
    NS_ENSURE_SUCCESS(rv, false);

    // set excepthook
    PyObject *excepthook = PyModule_GetAttrId("pymoz", &PyId_excepthook);
    if (!excepthook) {
        return false;
    }
    bool result = !!!PySys_SetObject(PyId_excepthook.string, excepthook);
    Py_DECREF(excepthook);
    return result;
}


bool
pyRuntime::SetupPython()
{
    //PY_ENSURE_SUCCESS(NS_ERROR_FAILURE, false, PyExc_Exception,
    //                  "Test: SetupPython");

    _Py_IDENTIFIER(showwarning);
    _Py_IDENTIFIER(__showwarning__);

    // init python exceptions
    mError = PyErr_NewException("pymoz.Error", NULL, NULL);
    if (!mError) {
        return false;
    }
    mJSError = PyErr_NewException("pymoz.JSError", mError, NULL);
    mXPCOMError = PyErr_NewException("pymoz.XPCOMError", mError, NULL);
    if (!(mJSError && mXPCOMError)) {
        return false;
    }
    mJSWarning = PyErr_NewException("pymoz.JSWarning", PyExc_Warning, NULL);
    if (!mJSWarning) {
        return false;
    }

    // set showwarning
    PyObject *__showwarning__ = PyModule_GetAttrId("warnings", &PyId_showwarning);
    PyObject *showwarning = PyModule_GetAttrId("pymoz", &PyId_showwarning);
    if (!(__showwarning__ && showwarning)) {
        Py_XDECREF(showwarning);
        Py_XDECREF(__showwarning__);
        return false;
    }
    bool result = !!!PyModule_SetAttrId("warnings", &PyId___showwarning__,
                                        __showwarning__);
    if (result) {
        result = !!!PyModule_SetAttrId("warnings", &PyId_showwarning,
                                       showwarning);
    }
    Py_DECREF(showwarning);
    Py_DECREF(__showwarning__);

    if (result) {
        // set sys path
        nsCOMPtr<nsIFile> aLibFile;
        nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_PROCESS_DIR,
                                             getter_AddRefs(aLibFile));
        PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                          "Failed to get current path");

        rv = aLibFile->AppendNative(NS_LITERAL_CSTRING("site-packages"));
        PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                          "Failed to append 'site-packages' to current path");

        nsCString aLibPath;
        rv = aLibFile->GetNativePath(aLibPath);
        PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                          "Failed to get updated current path");

        PyObject *pysyspath = PySys_GetObject("path"); // borrowed
        if (!pysyspath) {
            PyErr_SetString(mError, "Failed to get sys.path");
            return false;
        }

        PyObject *pylibpath = PyUnicode_DecodeFSDefault(aLibPath.get());
        if (!pylibpath) {
            return false;
        }
        result = !!!PyList_Insert(pysyspath, 0, pylibpath);
        Py_DECREF(pylibpath);
    }

    return result;
}


bool
pyRuntime::SetupRuntime()
{
    //PY_ENSURE_SUCCESS(NS_ERROR_FAILURE, false, PyExc_Exception,
    //                  "Test: SetupRuntime");

    nsresult rv = NS_ERROR_FAILURE;

    // JSContextStack
    mJSContextStack =
        do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to get nsIJSContextStack");

    // JSRuntimeService
    mJSRuntimeService =
        do_GetService("@mozilla.org/js/xpc/RuntimeService;1", &rv);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to get nsIJSRuntimeService");

    // JSRuntime
    rv = mJSRuntimeService->GetRuntime(&mJSRuntime);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to get JSRuntime");

    // caches
    mModules.Init();
    mPyObjects.Init();
    mJSObjects.Init();

    return true;
}


bool
pyRuntime::SetupWrappers()
{
    //PY_ENSURE_SUCCESS(NS_ERROR_FAILURE, false, PyExc_Exception,
    //                  "Test: SetupWrappers");

    bool result = PyJSObject_InitTypes();
    if (result) {
        JSContext *cx = GetJSContext();
        if (!cx) {
            return false;
        }
        JSAutoRequest ar(cx);
        JSObject *glob = JS_GetGlobalObject(cx);
        if (!glob) {
            PyErr_SetString(mJSError, "Failed to get global object");
            return false;
        }
        mJSPyTypeProto = JSPyObject_InitPrototype(cx, glob);
        if (!mJSPyTypeProto) {
            PyErr_SetString(mJSError, "Failed to get init JS prototype");
            return false;
        }
        result = HoldJSObject(&this->mJSPyTypeProto);
        if (result && !JS_DeleteProperty(cx, glob, "JSPyType")) {
            PyErr_SetString(mJSError,
                            "Failed to delete prototype from global object");
            result = false;
        }
    }

    return result;
}


bool
pyRuntime::SetupPymoz()
{
    //PY_ENSURE_SUCCESS(NS_ERROR_FAILURE, false, PyExc_Exception,
    //                  "Test: SetupPymoz");

    static const char *XPCOMUtils_uri = "resource://gre/modules/XPCOMUtils.jsm";
    _Py_IDENTIFIER(XPCOMUtils);
    _Py_IDENTIFIER(Components);

    PyObject *pymoz = PyImport_AddModule("pymoz"); // borrowed
    if (!pymoz) {
        return false;
    }

    // exceptions
    if (
        PyModule_AddPyObject(pymoz, "Error", mError) ||
        PyModule_AddPyObject(pymoz, "JSError", mJSError) ||
        PyModule_AddPyObject(pymoz, "XPCOMError", mXPCOMError) ||
        PyModule_AddPyObject(pymoz, "JSWarning", mJSWarning)
       ) {
        return false;
    }

    // XPCOMUtils
    PyObject *XPCOMUtils_global = JSImportModule(XPCOMUtils_uri, pymoz);
    if (!XPCOMUtils_global) {
        return false;
    }
    mXPCOMUtils = _PyObject_GetAttrId(XPCOMUtils_global, &PyId_XPCOMUtils);
    Py_DECREF(XPCOMUtils_global);
    if (!mXPCOMUtils) {
        return false;
    }

    // Components
    PyObject *window = JSGetGlobal();
    if (!window) {
        return false;
    }
    mComponents = _PyObject_GetAttrId(window, &PyId_Components);
    Py_DECREF(window);
    if (!mComponents) {
        return false;
    }
    bool result = !!!PyModule_AddPyObject(pymoz, PyId_Components.string,
                                          mComponents);

    return result;
}


bool
pyRuntime::SetupObserver()
{
    //PY_ENSURE_SUCCESS(NS_ERROR_FAILURE, false, PyExc_Exception,
    //                  "Test: SetupObserver");

    nsresult rv = NS_ERROR_FAILURE;

    // observing
    nsCOMPtr<nsIObserverService> obsSvc =
        do_GetService("@mozilla.org/observer-service;1", &rv);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to get nsIObserverService");
    rv = obsSvc->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to add observer");

    return true;
}


bool
pyRuntime::Setup()
{
    if (PreSetup()) {
        /*
           past this point report errors in Python
           (mResult's value makes a difference)
        */
        if (SetupPython() &&
            SetupRuntime() &&
            SetupWrappers() &&
            SetupPymoz() &&
            SetupObserver()) {
            return true;
        }
        mResult = NS_ERROR_FAILURE;
        ReportError();
    }

    return false;
}


void
pyRuntime::Clear()
{
    if (Py_IsInitialized()) {

        if (PyErr_Occurred()) {
            PyErr_Print();
        }

        //cleanup js globals
        if (mJSRuntime && mJSPyTypeProto) {
            JSContext *acx;
            JSContext *iterp = NULL;
            while ((acx = JS_ContextIterator(mJSRuntime, &iterp))) {
                JSGlobal_CleanupPyObject(acx);
            }
        }

        //cleanup pymoz
        PyObject *pymoz = PyImport_AddModule("pymoz"); // borrowed
        if (pymoz) {
            PyObject *pymoz_dict = PyModule_GetDict(pymoz); // borrowed
            if (pymoz_dict) {
                PyDict_CleanupJSObject(pymoz_dict);
            }
        }

        //cleanup __main__
        PyObject *main = PyImport_AddModule("__main__"); // borrowed
        if (main) {
            PyObject *main_dict = PyModule_GetDict(main); // borrowed
            if (main_dict) {
                PyDict_CleanupJSObject(main_dict);
            }
        }

        //cleanup python globals
        PyObject *globals = PyEval_GetGlobals(); // borrowed
        if (globals) {
            PyDict_CleanupJSObject(globals);
        }

        // clear JSObjects
        if (mJSObjects.IsInitialized()) {
            mJSObjects.Clear();
        }

        // clear PyObjects
        if (mPyObjects.IsInitialized()) {
            mPyObjects.EnumerateRead(PyObjectCache_Clear, NULL);
            mPyObjects.Clear();
        }

        // clear Modules
        if (mModules.IsInitialized()) {
            mModules.EnumerateRead(ModuleCache_Clear, NULL);
            mModules.Clear();
        }

        //cleanup Components
        Py_CLEAR(mComponents);

        //cleanup XPCOMUtils
        Py_CLEAR(mXPCOMUtils);

        //cleanup exceptions
        Py_CLEAR(mJSWarning);
        Py_CLEAR(mXPCOMError);
        Py_CLEAR(mJSError);
        Py_CLEAR(mError);

        if (PyErr_Occurred()) {
            PyErr_Print();
        }

        if (mJSRuntime) {

            //garbage collection
            JS_GC(mJSRuntime);
            while (PyGC_Collect() > 0);

            //cleanup JSPyTypeProto
            if (mJSPyTypeProto) {
                DropJSObject(&this->mJSPyTypeProto);
                mJSPyTypeProto = NULL;
            }

            /* some explanation of why the following exists would be welcome
               (it has to do with finalizing PyJSObjects still held by Python
                after the JSRuntime has gone away) */
            JS_MapGCRoots(mJSRuntime, PymozGCRoot_Clear, NULL);
            mJSRuntime = NULL;
        }

        mResult = 0;
    }
}


/* pyRuntime - public helpers ----------------------------------------------- */

bool
pyRuntime::ConvertURL(const char *aSpec, nsACString& aPath)
{
    nsresult rv = NS_ERROR_FAILURE;
    bool isChrome = false;

    nsCOMPtr<nsIURI> aURI;
    rv = NS_NewURI(getter_AddRefs(aURI), aSpec);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to create URL");

    rv = aURI->SchemeIs("chrome", &isChrome);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to check URL's scheme");

    if (isChrome) {
        return ConvertChromeURL(aURI, aPath);
    }
    else {
        nsCOMPtr<nsIFileURL> aFileURL = do_QueryInterface(aURI, &rv);
        if (NS_SUCCEEDED(rv)) {
            return ConvertFileURL(aFileURL, aPath);
        }
        else {
            aPath.Assign(aSpec);
            return true;
        }
    }
}


void
pyRuntime::ReportError()
{
    bool need_tb = false;
    _Py_IDENTIFIER(print_file_and_line);
    PyObject *exctype, *excvalue, *exctraceback;

    PyErr_Fetch(&exctype, &excvalue, &exctraceback);
    PyErr_NormalizeException(&exctype, &excvalue, &exctraceback);
    if (!_PyObject_HasAttrId(excvalue, &PyId_print_file_and_line) &&
        !(exctraceback && PyTraceBack_Check(exctraceback))) {
        need_tb = true;
    }
    PyErr_Restore(exctype, excvalue, exctraceback);

    if (need_tb) {
        nsCOMPtr<nsIStackFrame> aLocation;
        mXPConnect->GetCurrentJSStack(getter_AddRefs(aLocation));
        if (aLocation) {
            char *filename = NULL, *name = NULL;
            int lineno = 0;
            aLocation->GetFilename(&filename);
            aLocation->GetName(&name);
            aLocation->GetLineNumber(&lineno);
            if (filename) {
                nsCString aPath;
                if (ConvertURL(filename, aPath)) {
                    PyTraceback_FromLocation(aPath.get(),
                                             name ? name : "<script>", lineno);
                }
            }
            if (name) {
                NS_Free(name);
            }
            if (filename) {
                NS_Free(filename);
            }
        }
    }

    PyErr_Print();
}


JSContext *
pyRuntime::GetJSContext()
{
    JSContext *cx = NULL;

    mJSContextStack->Peek(&cx);
    if (!cx) {
        cx = mJSContextStack->GetSafeJSContext();
    }

    if (!cx) {
        PyErr_SetString(mXPCOMError, "Failed to get JSContext");
    }

    return cx;
}


bool
pyRuntime::SetException(PyObject *aExcValue)
{
    nsresult rv = NS_ERROR_FAILURE;

    nsCOMPtr<nsIException> aCause;
    rv = mExceptionManager->GetCurrentException(getter_AddRefs(aCause));
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to get pending exception");

    nsCOMPtr<nsIException> aExc =
        pyException::Create(aExcValue,
                            NS_FAILED(mResult) ? mResult : NS_ERROR_FAILURE,
                            aCause);
    if (!aExc) {
        return false;
    }

    if (NS_FAILED(mResult)) {
        rv = mExceptionManager->SetCurrentException(aExc);
        PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                          "Failed to set pending exception");
    }
    else {
        JSContext *cx = GetJSContext();
        if (!cx) {
            return false;
        }
        JSAutoRequest ar(cx);
        nsCOMPtr<nsIXPConnectJSObjectHolder> aHolder;
        rv = mXPConnect->WrapNative(cx, JS_GetGlobalObject(cx), aExc,
                                    NS_GET_IID(nsIException),
                                    getter_AddRefs(aHolder));
        PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                          "Failed to wrap nsIException");
        JSObject* jsobj;
        rv = aHolder->GetJSObject(&jsobj);
        PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                          "Failed to get JSObject from Holder");
        JS_SetPendingException(cx, OBJECT_TO_JSVAL(jsobj));
    }

    return true;
}


bool
pyRuntime::ReportWarning(PyObject *aMessage, PyObject *aCategory,
                         PyObject *aFilename, PyObject *aLineno,
                         PyObject *aLine)
{
    nsresult rv = NS_ERROR_FAILURE;

    nsCOMPtr<nsIScriptError> aWarn =
        pyWarning::Create(aMessage, aCategory, aFilename, aLineno, aLine);
    if (!aWarn) {
        return false;
    }

    nsCOMPtr<nsIConsoleService> consSvc =
        do_GetService("@mozilla.org/consoleservice;1", &rv);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to get nsIConsoleService");

    rv = consSvc->LogMessage(aWarn);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to log warning");

    return true;
}


bool
pyRuntime::HoldJSObject(JSObject **jsobj)
{
    bool result = js_AddGCThingRootRT(mJSRuntime, (void **)jsobj, "pymoz");
    if (!result) {
        PyErr_NoMemory();
    }
    return result;
}


void
pyRuntime::DropJSObject(JSObject **jsobj)
{
    if (mJSRuntime) {
        js_RemoveRoot(mJSRuntime, (void *)jsobj);
    }
}


bool
pyRuntime::GetObject(JSObject *jsobj, PyObject **pyobj)
{
    return mPyObjects.Get(jsobj, pyobj);

}


void
pyRuntime::PutObject(JSObject *jsobj, PyObject *pyobj)
{
    mPyObjects.Put(jsobj, pyobj);

}


bool
pyRuntime::GetObject(PyObject *pyobj, JSObject **jsobj)
{
    JSHolder *holder = NULL;
    if (mJSObjects.Get(pyobj, &holder)) {
        *jsobj = holder->GetPrivate();
        return true;
    }
    return false;

}


void
pyRuntime::PutObject(PyObject *pyobj, JSObject *jsobj)
{
    JSHolder *holder = new JSHolder(jsobj);
    if (holder && holder->Hold()) {
        mJSObjects.Put(pyobj, holder);
    }
    else {
        NS_RUNTIMEABORT("OOM");
    }

}


PyObject *
pyRuntime::JSImportModule(const char *uri, PyObject *target)
{
    nsresult rv = NS_ERROR_FAILURE;
    nsCOMPtr<xpcIJSModuleLoader> moduleloader =
        do_GetService("@mozilla.org/moz/jsloader;1", &rv);
    PY_ENSURE_SUCCESS(rv, NULL, mXPCOMError,
                      "Failed to get xpcIJSModuleLoader");

    nsCString aLocation;
    aLocation.Assign(uri);

    JSContext *cx = GetJSContext();
    if (!cx) {
        return NULL;
    }

    JSObject *aTarget = JSPyObject_NewOrUsed(cx, target);
    if (!aTarget) {
        return NULL;
    }

    jsval retval;
    rv = moduleloader->Import(aLocation, OBJECT_TO_JSVAL(aTarget), cx, 1,
                              &retval);
    PY_ENSURE_SUCCESS(rv, NULL, mXPCOMError,
                      "Failed to import module");

    return PyObject_FromJSVal(cx, retval);
}


PyObject *
pyRuntime::JSGetGlobal()
{
    JSContext *cx = GetJSContext();
    if (!cx) {
        return NULL;
    }
    JSAutoRequest ar(cx);
    return PyJSObject_NewOrUsed(cx, JS_GetGlobalObject(cx));
}


PyObject *
pyRuntime::JSInstanceOf(PyObject *obj, PyObject *proto)
{
    JSContext *cx = GetJSContext();
    if (!cx) {
        return NULL;
    }

    JSObject *aProto = JSPyObject_NewOrUsed(cx, proto);
    if (!aProto) {
        return NULL;
    }

    jsval aObj = PyObject_AsJSVal(cx, obj);
    if (JSVAL_IS_VOID(aObj)) {
        return NULL;
    }

    JSBool res = JS_FALSE;
    if (!JS_HasInstance(cx, aProto, aObj, &res)) {
        return NULL;
    }
    PyObject *result = res ? Py_True : Py_False;
    Py_INCREF(result);
    return result;
}


/* pyRuntime - private utils ------------------------------------------------ */

bool
pyRuntime::ConvertFileURL(nsIFileURL *aFileURL, nsACString& aPath)
{
    nsresult rv = NS_ERROR_FAILURE;

    nsCOMPtr<nsIFile> aFile;
    rv = aFileURL->GetFile(getter_AddRefs(aFile));
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to get file");

    rv = aFile->GetNativePath(aPath);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to get native path");

    return true;
}


bool
pyRuntime::ConvertChromeURL(nsIURI *aChromeURL, nsACString& aPath)
{
    nsresult rv = NS_ERROR_FAILURE;

    nsCOMPtr<nsIChromeRegistry> crSvc =
        do_GetService(NS_CHROMEREGISTRY_CONTRACTID, &rv);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to get nsIChromeRegistry");

    nsCOMPtr<nsIURI> aPathURL;
    rv = crSvc->ConvertChromeURL(aChromeURL, getter_AddRefs(aPathURL));
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to convert chrome URL");

    nsCOMPtr<nsIFileURL> aFileURL = do_QueryInterface(aPathURL, &rv);
    PY_ENSURE_SUCCESS(rv, false, mXPCOMError,
                      "Failed to QueryInterface nsIURI to nsIFileURL");

    return ConvertFileURL(aFileURL, aPath);
}


already_AddRefed<nsIFile>
pyRuntime::GetFile(const nsACString& aSpec, const char *aOriginCharset,
                   nsIURI *aBaseURI, nsACString& aPath)
{
    nsresult rv = NS_ERROR_FAILURE;
    bool isChrome = false;

    nsCOMPtr<nsIURI> aChromeURL;
    rv = NS_NewURI(getter_AddRefs(aChromeURL), aSpec, aOriginCharset, aBaseURI);
    PY_ENSURE_SUCCESS(rv, NULL, mXPCOMError,
                      "Failed to create URL");

    rv = aChromeURL->SchemeIs("chrome", &isChrome);
    PY_ENSURE_SUCCESS(rv, NULL, mXPCOMError,
                      "Failed to check URL's scheme");

    if (!isChrome) {
        PyErr_SetString(mError, "python 'src' attribute must be a chrome URL");
        return NULL;
    }

    nsCOMPtr<nsIChromeRegistry> crSvc =
        do_GetService(NS_CHROMEREGISTRY_CONTRACTID, &rv);
    PY_ENSURE_SUCCESS(rv, NULL, mXPCOMError,
                      "Failed to get nsIChromeRegistry");

    nsCOMPtr<nsIURI> aPathURL;
    rv = crSvc->ConvertChromeURL(aChromeURL, getter_AddRefs(aPathURL));
    PY_ENSURE_SUCCESS(rv, NULL, mXPCOMError,
                      "Failed to convert chrome URL");

    nsCOMPtr<nsIFileURL> aFileURL = do_QueryInterface(aPathURL, &rv);
    PY_ENSURE_SUCCESS(rv, NULL, mXPCOMError,
                      "Failed to QueryInterface nsIURI to nsIFileURL");

    nsCOMPtr<nsIFile> aFile;
    rv = aFileURL->GetFile(getter_AddRefs(aFile));
    PY_ENSURE_SUCCESS(rv, NULL, mXPCOMError,
                      "Failed to get file");

    rv = aFile->GetNativePath(aPath);
    PY_ENSURE_SUCCESS(rv, NULL, mXPCOMError,
                      "Failed to get native path");

    return aFile.forget();
}


bool
pyRuntime::UpdateJSGlobal()
{
    PyObject *globals = Py_GetGlobals(); // borrowed
    PyObject *window = JSGetGlobal();
    PyObject *list = NULL;

    if (!(globals && window)) {
        Py_XDECREF(window);
        return false;
    }
    list = PyDict_GetItemString(globals, "__window__"); // borrowed
    if (list) {
        if (!PyList_CheckExact(list)) {
            PyErr_SetString(PyExc_TypeError, "__window__ must be a list");
            goto fail;
        }
        Py_ssize_t size = PyList_GET_SIZE(list);
        PyObject *name, *attr;
        for (Py_ssize_t i = 0; i < size; i++) {
            name = PyList_GET_ITEM(list, i); // borrowed
            if (!PyUnicode_Check(name)) {
                PyErr_SetString(PyExc_TypeError,
                                "__window__'s items must be string objects");
                goto fail;
            }
            attr = PyDict_GetItemWithError(globals, name); // borrowed
            if (!attr) {
                if (!PyErr_Occurred()) {
                    PyErr_Format(PyExc_NameError,
                                 "global name '%U' is not defined", name);
                }
                goto fail;
            }
            if (PyObject_SetAttr(window, name, attr)) {
                goto fail;
            }
        }
        PyDict_DelItemString(globals, "__window__");
    }
    Py_DECREF(window);
    return true;

fail:
    PyDict_DelItemString(globals, "__window__");
    Py_XDECREF(window);
    return false;
}


/*******************************************************************************
* JSAutoContextPusher
*******************************************************************************/

JSAutoContextPusher::JSAutoContextPusher(JSContext *cx)
{
    pyRuntime::sPyRuntime->mJSContextStack->Push(cx);
}


JSAutoContextPusher::~JSAutoContextPusher()
{
    JSContext *cx = NULL;
    pyRuntime::sPyRuntime->mJSContextStack->Pop(&cx);
}


/*******************************************************************************
* JSHolder
*******************************************************************************/

JSHolder::JSHolder(JSObject *jsobj)
    : mJSObj(jsobj)
{
}


JSHolder::~JSHolder()
{
    if (mJSObj) {
        pyRuntime::sPyRuntime->DropJSObject(&mJSObj);
        mJSObj = NULL;
    }
}


bool
JSHolder::Hold()
{
    if (mJSObj) {
        if (pyRuntime::sPyRuntime->HoldJSObject(&mJSObj)) {
            return true;
        }
        else {
            mJSObj = NULL;
        }
    }
    return false;
}


JSObject *
JSHolder::GetPrivate()
{
    return mJSObj;
}


/*******************************************************************************
* pyModule
*******************************************************************************/

already_AddRefed<nsIFactory>
pyModule::GetFactory(const mozilla::Module& module,
                     const mozilla::Module::CIDEntry& entry)
{
    const pyModule& self = static_cast<const pyModule&>(module);

    nsCOMPtr<nsIFactory> f;
    if (NS_FAILED(self.getfactoryobj->Get(*entry.cid, getter_AddRefs(f)))) {
        PyErr_SetString(pyRuntime::sPyRuntime->mXPCOMError,
                        "Failed to get factory");
        pyRuntime::sPyRuntime->mResult = NS_ERROR_FAILURE;
        pyRuntime::sPyRuntime->ReportError();
        return NULL;
    }
    return f.forget();
}


bool
pyModule::Init(nsIFile *aFile, const char *aPath)
{
    nsresult rv = NS_ERROR_FAILURE;
    _Py_IDENTIFIER(components);
    _Py_IDENTIFIER(generateNSGetFactory);

    // name
    nsCOMPtr<nsIURI> aURI;
    rv = NS_NewFileURI(getter_AddRefs(aURI), aFile);
    PY_ENSURE_SUCCESS(rv, false, pyRuntime::sPyRuntime->mXPCOMError,
                      "Failed to create module URL");

    nsCOMPtr<nsIURL> aURL = do_QueryInterface(aURI, &rv);
    PY_ENSURE_SUCCESS(rv, false, pyRuntime::sPyRuntime->mXPCOMError,
                      "Failed to QueryInterface nsIURI to nsIURL");

    nsCString aName;
    rv = aURL->GetFileBaseName(aName);
    PY_ENSURE_SUCCESS(rv, false, pyRuntime::sPyRuntime->mXPCOMError,
                      "Failed to get module name");

    // module
    FILE *fp;
    if (NS_FAILED(aFile->OpenANSIFileDesc("r", &fp))) {
        PyErr_SetFromErrnoWithFilename(PyExc_OSError, aPath);
        return false;
    }
    mModule = ImportModuleFromFile(fp, aPath, aName.get());
    fclose(fp);
    if (!mModule) {
        return false;
    }

    // factory
    PyObject *components = _PyObject_GetAttrId(mModule, &PyId_components);
    if (!components) {
        return false;
    }
    if (!PyList_Check(components)) {
        Py_DECREF(components);
        PyErr_SetString(PyExc_TypeError,
                        "module attribute 'components' must be a list");
        return false;
    }

    PyObject *factory =
        _PyObject_CallMethodObjIdArgs(pyRuntime::sPyRuntime->mXPCOMUtils,
                                      &PyId_generateNSGetFactory, components,
                                      NULL);
    Py_DECREF(components);
    if (!factory) {
        return false;
    }

    JSContext *cx = pyRuntime::sPyRuntime->GetJSContext();
    if (!cx) {
        Py_DECREF(factory);
        return false;
    }

    JSObject *jsFactory = JSPyObject_NewOrUsed(cx, factory);
    Py_DECREF(factory);
    if (!jsFactory) {
        return false;
    }

    rv = pyRuntime::sPyRuntime->mXPConnect->WrapJS(cx, jsFactory,
                                                   NS_GET_IID(xpcIJSGetFactory),
                                                   getter_AddRefs(getfactoryobj));
    PY_ENSURE_SUCCESS(rv, false, pyRuntime::sPyRuntime->mXPCOMError,
                      "Failed to wrap factory");

    return true;
}


PyObject *
pyModule::ImportModuleFromFile(FILE *fp, const char *path, const char *name)
{
    PyArena *arena = PyArena_New();
    if (!arena) {
        return NULL;
    }

    struct _mod *mod = PyParser_ASTFromFile(fp, path, NULL, Py_file_input,
                                            0, 0, NULL, NULL, arena);
    if (!mod) {
        PyArena_Free(arena);
        return NULL;
    }

    PyObject *code = (PyObject *)PyAST_CompileEx(mod, path, NULL, -1, arena);
    PyArena_Free(arena);
    if (!code) {
        return NULL;
    }

    PyObject *module = PyImport_ExecCodeModule(const_cast<char *>(name), code);
    Py_DECREF(code);
    if (module) {
        PyObject *modules = PyImport_GetModuleDict(); // borrowed
        if (PyDict_DelItemString(modules, name)) {
            Py_FatalError("import: deleting existing key in sys.modules failed");
        }
    }

    return module;
}
